package io

import (
	"bufio"
	"io"
	"os"
	"strconv"
	"strings"
	"time"
)

type IniSection map[string]string
type SimpleIniReader map[string]IniSection

func ReadIni(r io.Reader) (SimpleIniReader, error) {
	buf := bufio.NewReader(r)
	ir := make(SimpleIniReader)
	sect := make(IniSection)
	ir[""] = sect
	for {
		line, err := buf.ReadString('\n')
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		line = strings.TrimSpace(line)
		if len(line) < 1 || line[0] == ';' || line[0] == '#' {
			continue
		}
		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
			key := line[1:line[len(line)-1]]
			key = strings.TrimSpace(key)
			sect = make(IniSection)
			ir[key] = sect
			continue
		}
		k, v, ok := strings.Cut(line, "=")
		if ok {
			k = strings.TrimSpace(k)
			v = strings.TrimSpace(v)
			sect[k] = v
		}
	}
	return ir, nil
}

func ReadIniFile(f string) (SimpleIniReader, error) {
	fp, err := os.Open(f)
	if err != nil {
		return nil, err
	}
	defer fp.Close()
	return ReadIni(fp)
}

func (x IniSection) Get(key string) (string, bool) {
	s, ok := x[key]
	return s, ok
}

func (x IniSection) MustGet(key string, defaultValue string) string {
	if s, ok := x.Get(key); ok {
		return s
	}
	return defaultValue
}

func (x IniSection) GetSlice(key, sep string) ([]string, bool) {
	s, ok := x[key]
	if ok {
		return strings.Split(s, sep), ok
	}
	return nil, false
}

func (x IniSection) MustGetSlice(key, sep string, defaultValue string) []string {
	if s, ok := x.GetSlice(key, sep); ok {
		return s
	}
	return strings.Split(defaultValue, sep)
}

func (x IniSection) GetInt(key string) (int, bool) {
	s, ok := x[key]
	if ok {
		i, err := strconv.ParseInt(s, 0, 0)
		if err != nil {
			return 0, false
		}
		return int(i), ok
	}
	return 0, false
}

func (x IniSection) MustGetInt(key string, defaultValue int) int {
	if s, ok := x.GetInt(key); ok {
		return s
	}
	return defaultValue
}

func (x IniSection) GetUint(key string) (uint, bool) {
	s, ok := x[key]
	if ok {
		i, err := strconv.ParseUint(s, 0, 0)
		if err != nil {
			return 0, false
		}
		return uint(i), ok
	}
	return 0, false
}

func (x IniSection) MustGetUint(key string, defaultValue uint) uint {
	if s, ok := x.GetUint(key); ok {
		return s
	}
	return defaultValue
}

func (x IniSection) GetFloat(key string) (float64, bool) {
	s, ok := x[key]
	if ok {
		i, err := strconv.ParseFloat(s, 64)
		if err != nil {
			return 0, false
		}
		return i, ok
	}
	return 0, false
}

func (x IniSection) MustGetFloat(key string, defaultValue float64) float64 {
	if s, ok := x.GetFloat(key); ok {
		return s
	}
	return defaultValue
}

func (x IniSection) GetDuration(key string) (time.Duration, bool) {
	s, ok := x[key]
	if ok {
		d, err := time.ParseDuration(s)
		if err != nil {
			return 0, false
		}
		return d, ok
	}
	return 0, false
}

func (x IniSection) MustGetDuration(key string, defaultValue time.Duration) time.Duration {
	if s, ok := x.GetDuration(key); ok {
		return s
	}
	return defaultValue
}

func (x IniSection) GetTimeFormat(key, layout string) (time.Time, bool) {
	s, ok := x[key]
	if ok {
		t, err := time.Parse(layout, s)
		if err != nil {
			return time.Time{}, false
		}
		return t, ok
	}
	return time.Time{}, false
}

func (x IniSection) MustGetTimeFormat(key, layout string, defaultValue time.Time) time.Time {
	if s, ok := x.GetTimeFormat(key, layout); ok {
		return s
	}
	return defaultValue
}

func (x SimpleIniReader) Section(sect string) (IniSection, bool) {
	s, ok := x[sect]
	return s, ok
}

func (x SimpleIniReader) Get(sect, key string) (string, bool) {
	return x[sect].Get(key)
}

func (x SimpleIniReader) MustGet(sect, key string, defaultValue string) string {
	if v, ok := x.Get(sect, key); ok {
		return v
	}
	return defaultValue
}

func (x SimpleIniReader) GetSlice(sect, key, sep string) ([]string, bool) {
	return x[sect].GetSlice(key, sep)
}

func (x SimpleIniReader) MustGetSlice(sect, key, sep string, defaultValue string) []string {
	return x[sect].MustGetSlice(key, sep, defaultValue)
}

func (x SimpleIniReader) GetInt(sect, key string) (int, bool) {
	return x[sect].GetInt(key)
}

func (x SimpleIniReader) MustGetInt(sect, key string, defaultValue int) int {
	return x[sect].MustGetInt(key, defaultValue)
}

func (x SimpleIniReader) GetUint(sect, key string) (uint, bool) {
	return x[sect].GetUint(key)
}

func (x SimpleIniReader) MustGetUint(sect, key string, defaultValue uint) uint {
	return x[sect].MustGetUint(key, defaultValue)
}

func (x SimpleIniReader) GetFloat(sect, key string) (float64, bool) {
	return x[sect].GetFloat(key)
}

func (x SimpleIniReader) MustGetFloat(sect, key string, defaultValue float64) float64 {
	return x[sect].MustGetFloat(key, defaultValue)
}

func (x SimpleIniReader) GetDuration(sect, key string) (time.Duration, bool) {
	return x[sect].GetDuration(key)
}

func (x SimpleIniReader) MustGetDuration(sect, key string, defaultValue time.Duration) time.Duration {
	return x[sect].MustGetDuration(key, defaultValue)
}

// TimeFormat parses with given format and returns time.Time type value.
func (x SimpleIniReader) GetTimeFormat(sect, key, format string) (time.Time, bool) {
	return x[sect].GetTimeFormat(key, format)
}

func (x SimpleIniReader) MustGetTimeFormat(sect, key, format string, defaultValue time.Time) time.Time {
	return x[sect].MustGetTimeFormat(key, format, defaultValue)
}

// LoadOptions contains all customized options used for load data source(s).
type LoadOptions struct {
	FSections func(string) string
	FKeys     func(string) string
	// split string.
	FSlice func(string) []string
	// AllowShadows： true-取最后一个；false-第一个.
	AllowShadows bool
}

type IniOption func(*LoadOptions)

func WithIniInsensitive() IniOption {
	return func(x *LoadOptions) {
		x.FKeys = strings.ToLower
		x.FSections = strings.ToLower
	}
}

func WithIniInsensitiveKeys() IniOption {
	return func(x *LoadOptions) {
		x.FKeys = strings.ToLower
	}
}

func WithIniInsensitiveSections() IniOption {
	return func(x *LoadOptions) {
		x.FSections = strings.ToLower
	}
}

func WithIniSliceSep(sep string) IniOption {
	return func(x *LoadOptions) {
		x.FSlice = func(s string) []string {
			return strings.Split(s, sep)
		}
	}
}

func WithIniSlice(f func(string) []string) IniOption {
	return func(x *LoadOptions) {
		x.FSlice = f
	}
}

func WithIniAllowShadows(a bool) IniOption {
	return func(x *LoadOptions) {
		x.AllowShadows = a
	}
}

func iniStr(s string) string {
	return s
}

func iniStrs(s string) []string {
	return []string{s}
}
func iniOptions(opts ...IniOption) LoadOptions {
	o := LoadOptions{iniStr, iniStr, iniStrs, false}
	for _, opt := range opts {
		opt(&o)
	}
	return o
}

type IniReader struct {
	options LoadOptions
	sects   SimpleIniReader
}

func OpenIniReader(r io.Reader, opts ...IniOption) (*IniReader, error) {
	buf := bufio.NewReader(r)
	o := iniOptions(opts...)
	ir := make(SimpleIniReader)
	sect := make(IniSection)
	ir[""] = sect
	for {
		line, err := buf.ReadString('\n')
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		line = strings.TrimSpace(line)
		if len(line) < 1 || line[0] == ';' || line[0] == '#' {
			continue
		}
		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
			key := line[1:line[len(line)-1]]
			key = strings.TrimSpace(key)
			sect = make(IniSection)
			key = o.FSections(key)
			ir[key] = sect
			continue
		}
		k, v, ok := strings.Cut(line, "=")
		if ok {
			k = strings.TrimSpace(k)
			v = strings.TrimSpace(v)
			k = o.FKeys(k)
			sect[k] = v
		}
	}
	return &IniReader{o, ir}, nil
}

func OpenIniFile(f string, opts ...IniOption) (*IniReader, error) {
	fp, err := os.Open(f)
	if err != nil {
		return nil, err
	}
	defer fp.Close()
	return OpenIniReader(fp, opts...)
}

func (x *IniReader) Section(sect string) (IniSection, bool) {
	if x == nil {
		return nil, false
	}
	sect = x.options.FSections(sect)
	s, ok := x.sects[sect]
	return s, ok
}

func (x *IniReader) Get(sect, key string) (string, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return "", false
	}
	return s.Get(x.options.FKeys(key))
}

func (x *IniReader) MustGet(sect, key string, defaultValue string) string {
	if v, ok := x.Get(sect, key); ok {
		return v
	}
	return defaultValue
}

func (x *IniReader) GetSlice(sect, key string) ([]string, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return nil, false
	}
	v, ok := s.Get(x.options.FKeys(key))
	if !ok {
		return nil, false
	}
	return x.options.FSlice(v), true
}

func (x *IniReader) MustGetSlice(sect, key string, defaultValue string) []string {
	if v, ok := x.GetSlice(sect, key); ok {
		return v
	}
	return x.options.FSlice(defaultValue)
}

func (x *IniReader) GetInt(sect, key string) (int, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return 0, false
	}
	return s.GetInt(x.options.FKeys(key))
}

func (x *IniReader) MustGetInt(sect, key string, defaultValue int) int {
	if v, ok := x.GetInt(sect, key); ok {
		return v
	}
	return defaultValue
}

func (x *IniReader) GetUint(sect, key string) (uint, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return 0, false
	}
	return s.GetUint(x.options.FKeys(key))
}

func (x *IniReader) MustGetUint(sect, key string, defaultValue uint) uint {
	if v, ok := x.GetUint(sect, key); ok {
		return v
	}
	return defaultValue
}

func (x *IniReader) GetFloat(sect, key string) (float64, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return 0, false
	}
	return s.GetFloat(x.options.FKeys(key))
}

func (x *IniReader) MustGetFloat(sect, key string, defaultValue float64) float64 {
	if v, ok := x.GetFloat(sect, key); ok {
		return v
	}
	return defaultValue
}

func (x *IniReader) GetDuration(sect, key string) (time.Duration, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return 0, false
	}
	return s.GetDuration(x.options.FKeys(key))
}

func (x *IniReader) MustGetDuration(sect, key string, defaultValue time.Duration) time.Duration {
	if v, ok := x.GetDuration(sect, key); ok {
		return v
	}
	return defaultValue
}

// TimeFormat parses with given format and returns time.Time type value.
func (x *IniReader) GetTimeFormat(sect, key, format string) (time.Time, bool) {
	s, ok := x.Section(sect)
	if !ok {
		return time.Time{}, false
	}
	return s.GetTimeFormat(x.options.FKeys(key), format)
}

func (x *IniReader) MustGetTimeFormat(sect, key, format string, defaultValue time.Time) time.Time {
	if v, ok := x.GetTimeFormat(sect, key, format); ok {
		return v
	}
	return defaultValue
}
